home *** CD-ROM | disk | FTP | other *** search
/ Graphics Plus / Graphics Plus.iso / amiga / plotting / gnuplot3.lzh / gnuplot / help.c < prev    next >
C/C++ Source or Header  |  1991-07-24  |  18KB  |  677 lines

  1. /* GNUPLOT - help.c */
  2. /*
  3.  * Copyright (C) 1986, 1987, 1990, 1991   Thomas Williams, Colin Kelley
  4.  *
  5.  * Permission to use, copy, and distribute this software and its
  6.  * documentation for any purpose with or without fee is hereby granted, 
  7.  * provided that the above copyright notice appear in all copies and 
  8.  * that both that copyright notice and this permission notice appear 
  9.  * in supporting documentation.
  10.  *
  11.  * Permission to modify the software is granted, but not the right to
  12.  * distribute the modified code.  Modifications are to be distributed 
  13.  * as patches to released version.
  14.  *  
  15.  * This software is provided "as is" without express or implied warranty.
  16.  * 
  17.  *
  18.  * AUTHORS
  19.  * 
  20.  *   Original Software:
  21.  *     Thomas Williams,  Colin Kelley.
  22.  * 
  23.  *   Gnuplot 2.0 additions:
  24.  *       Russell Lang, Dave Kotz, John Campbell.
  25.  *
  26.  *   Gnuplot 3.0 additions:
  27.  *       Gershon Elber and many others.
  28.  * 
  29.  * Send your comments or suggestions to 
  30.  *  pixar!info-gnuplot@sun.com.
  31.  * This is a mailing list; to join it send a note to 
  32.  *  pixar!info-gnuplot-request@sun.com.  
  33.  * Send bug reports to
  34.  *  pixar!bug-gnuplot@sun.com.
  35.  */
  36.  
  37. #include <stdio.h>
  38.  
  39. extern int errno;
  40.  
  41. extern int strcmp();
  42. extern int strlen();
  43. extern char *strcpy();
  44. extern char *strncpy();
  45. extern char *strcat();
  46. extern char *strncat();
  47. extern char *getenv();
  48. extern FILE *fopen();
  49. extern char *malloc();
  50.  
  51. extern int instring();
  52.  
  53. #define    SAME    0    /* for strcmp() */
  54.  
  55. #include "help.h"    /* values passed back */
  56.  
  57. /* help -- help subsystem that understands defined keywords
  58. **
  59. ** Looks for the desired keyword in the help file at runtime, so you
  60. ** can give extra help or supply local customizations by merely editing
  61. ** the help file.
  62. **
  63. ** The original (single-file) idea and algorithm is by John D. Johnson,
  64. ** Hewlett-Packard Company.  Thanx and a tip of the Hatlo hat!
  65. **
  66. ** Much extension by David Kotz for use in gnutex, and then in gnuplot.
  67. ** Added output paging support, both unix and builtin. Rewrote completely
  68. ** to read helpfile into memory, avoiding reread of help file. 12/89.
  69. **
  70. ** Modified by Russell Lang to avoid reading completely into memory
  71. ** if MSDOS defined.  This uses much less memory.  6/91
  72. **
  73. ** The help file looks like this (the question marks are really in column 1):
  74. **
  75. **     ?topic
  76. **     This line is printed when the user wants help on "topic".
  77. **     ?keyword
  78. **     ?Keyword
  79. **     ?KEYWORD
  80. **     These lines will be printed on the screen if the user wanted
  81. **     help on "keyword", "Keyword", or "KEYWORD".  No casefolding is
  82. **    done on the keywords.
  83. **     ?subject
  84. **     ?alias
  85. **     This line is printed for help on "subject" and "alias".
  86. **     ?
  87. **    ??
  88. **     Since there is a null keyword for this line, this section
  89. **     is printed when the user wants general help (when a help
  90. **     keyword isn't given).  A command summary is usually here.
  91. **    Notice that the null keyword is equivalent to a "?" keyword
  92. **    here, because of the '?' and '??' topic lines above.
  93. **   If multiple keywords are given, the first is considered the 
  94. **   'primary' keyword. This affects a listing of available topics.
  95. **     ?last-subject
  96. **     Note that help sections are terminated by the start of the next
  97. **     '?' entry or by EOF.  So you can't have a leading '?' on a line
  98. **     of any help section.  You can re-define the magic character to
  99. **    recognize in column 1, though, if '?' is too useful.  (Try ^A.)
  100. */
  101.  
  102. #define    KEYFLAG    '?'    /* leading char in help file topic lines */
  103.  
  104. /*
  105. ** Calling sequence:
  106. **    int result;        # 0 == success
  107. **    char *keyword;        # topic to give help on
  108. **    char *pathname;        # path of help file
  109. **      int subtopics;        # set to TRUE if only subtopics to be listed
  110. **                # returns TRUE if subtopics were found
  111. **    result = help(keyword, pathname, &subtopics);
  112. ** Sample:
  113. **    cmd = "search\n";
  114. **    helpfile = "/usr/local/lib/program/program.help";
  115. **    subtopics = FALSE;
  116. **    if (help(cmd, helpfile, &subtopics) != H_FOUND)
  117. **        printf("Sorry, no help for %s", cmd);
  118. **
  119. **
  120. ** Speed this up by replacing the stdio calls with open/close/read/write.
  121. */
  122. #ifdef    WDLEN
  123. #  define    PATHSIZE    WDLEN
  124. #else
  125. #  define    PATHSIZE    BUFSIZ
  126. #endif
  127.  
  128. typedef int boolean;
  129. #ifndef TRUE
  130. #define TRUE (1)
  131. #define FALSE (0)
  132. #endif
  133.  
  134. typedef struct line_s LINEBUF;
  135. struct line_s {
  136.     char *line;            /* the text of this line */
  137.     LINEBUF *next;            /* the next line */
  138. };
  139.  
  140. typedef struct linkey_s LINKEY;
  141. struct linkey_s {
  142.     char *key;                /* the name of this key */
  143.     long pos;                /* ftell position */
  144.     LINEBUF *text;            /* the text for this key */
  145.     boolean primary;        /* TRUE -> is a primary name for a text block */
  146.     LINKEY *next;            /* the next key in linked list */
  147. };
  148.  
  149. typedef struct key_s KEY;
  150. struct key_s {
  151.     char *key;                /* the name of this key */
  152.     long pos;                /* ftell position */
  153.     LINEBUF *text;            /* the text for this key */
  154.     boolean primary;        /* TRUE -> is a primary name for a text block */
  155. };
  156. static LINKEY *keylist = NULL;    /* linked list of keys */
  157. static KEY *keys = NULL;        /* array of keys */
  158. static int keycount = 0;        /* number of keys */
  159. static FILE *helpfp = NULL;
  160.  
  161. static int LoadHelp();
  162. static void sortkeys();
  163. static int keycomp();
  164. static LINEBUF *storeline();
  165. static LINKEY *storekey();
  166. static KEY *FindHelp();
  167. static boolean Ambiguous();
  168.  
  169. /* Help output */
  170. static void PrintHelp();
  171. static void ShowSubtopics();
  172. static void StartOutput();
  173. static void OutLine();
  174. static void EndOutput();
  175. static FILE *outfile;        /* for unix pager, if any */
  176. static int pagelines;        /* count for builtin pager */
  177. #define SCREENSIZE 24        /* lines on screen (most have at least 24) */
  178.  
  179. /* help:
  180.  * print a help message 
  181.  * also print available subtopics, if subtopics is TRUE
  182.  */
  183. help(keyword, path, subtopics)
  184.     char *keyword;        /* on this topic */
  185.     char *path;            /* from this file */
  186.     boolean *subtopics;    /* (in) - subtopics only? */
  187.                         /* (out) - are there subtopics? */
  188. {
  189.     static char oldpath[PATHSIZE] = "";    /* previous help file */
  190.     int status;            /* result of LoadHelp */
  191.     KEY *key;            /* key that matches keyword */
  192.  
  193.     /*
  194.     ** Load the help file if necessary (say, first time we enter this routine,
  195.     ** or if the help file changes from the last time we were called).
  196.     ** Also may occur if in-memory copy was freed.
  197.     ** Calling routine may access errno to determine cause of H_ERROR.
  198.     */
  199.     errno = 0;
  200.     if (strncmp(oldpath, path, PATHSIZE) != SAME)
  201.      FreeHelp();
  202.     if (keys == NULL) {
  203.        status = LoadHelp(path);
  204.        if (status == H_ERROR)
  205.         return(status);
  206.  
  207.        /* save the new path in oldpath */
  208.        if (strlen(path) < PATHSIZE)
  209.         (void) strcpy(oldpath, path);
  210.        else {                /* not enough room in oldpath, sigh */
  211.           (void) strncpy(oldpath, path, PATHSIZE - 1);
  212.           oldpath[PATHSIZE - 1] = '\0';
  213.        }
  214.     }
  215.  
  216.     /* look for the keyword in the help file */
  217.     key = FindHelp(keyword);
  218.     if (key != NULL) {
  219.        /* found the keyword: print help and return */
  220.        PrintHelp(key, subtopics);
  221.        status = H_FOUND;
  222.     } else {
  223.        status = H_NOTFOUND;
  224.     }
  225.  
  226.     return(status);
  227. }
  228.  
  229. /* we only read the file once, into memory
  230.  * except for MSDOS when we don't read all the file -
  231.  * just the keys and location of the text
  232.  */
  233. static int
  234. LoadHelp(path)
  235.     char *path;
  236. {
  237.     LINKEY *key;            /* this key */
  238.     long pos;                /* ftell location within help file */
  239.     char buf[BUFSIZ];        /* line from help file */
  240.     LINEBUF *head;            /* head of text list  */
  241.     LINEBUF *firsthead = NULL;
  242.     boolean primary;        /* first ? line of a set is primary */
  243.     boolean flag;
  244.  
  245.     if ((helpfp = fopen(path, "r")) == NULL) {
  246.        /* can't open help file, so error exit */
  247.        return (H_ERROR);
  248.     }
  249.  
  250.     /*
  251.     ** The help file is open.  Look in there for the keyword.
  252.     */
  253.     (void) fgets(buf, BUFSIZ - 1, helpfp);
  254.     while (!feof(helpfp)) {
  255.        /*
  256.         ** Make an entry for each synonym keyword
  257.         */
  258.        primary = TRUE;
  259.        while (buf[0] == KEYFLAG) {
  260.           key = storekey(buf+1);    /* store this key */
  261.           key->primary = primary;
  262.           key->text = NULL;            /* fill in with real value later */
  263.           key->pos = 0;                /* fill in with real value later */
  264.           primary = FALSE;
  265.           pos = ftell(helpfp);
  266.           if (fgets(buf, BUFSIZ - 1, helpfp) == (char *)NULL)
  267.             break;
  268.        }
  269.        /*
  270.         ** Now store the text for this entry.
  271.         ** buf already contains the first line of text.
  272.         */
  273. #ifndef MSDOS
  274.        firsthead = storeline(buf);
  275.        head = firsthead;
  276. #endif
  277.        while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  278.         && (buf[0] != KEYFLAG) ){
  279. #ifndef MSDOS
  280.           /* save text line */
  281.           head->next = storeline(buf);
  282.           head = head->next;
  283. #endif
  284.        }
  285.        /* make each synonym key point to the same text */
  286.        do {
  287.           key->pos = pos;
  288.           key->text = firsthead;
  289.           flag = key->primary;
  290.           key = key->next;
  291.        } while ( flag!=TRUE  &&  key!=NULL );
  292.     }
  293. #ifndef MSDOS
  294.     (void) fclose(helpfp);
  295. #endif
  296.  
  297.     /* we sort the keys so we can use binary search later */
  298.     sortkeys();
  299.     return(H_FOUND); /* ok */
  300. }
  301.  
  302. /* make a new line buffer and save this string there */
  303. static LINEBUF *
  304. storeline(text)
  305.     char *text;
  306. {
  307.     LINEBUF *new;
  308.  
  309.     new = (LINEBUF *)malloc(sizeof(LINEBUF));
  310.     if (new == NULL)
  311.      int_error("not enough memory to store help file", -1);
  312.     if (text != NULL) {
  313.        new->line = (char *) malloc((unsigned int)(strlen(text)+1));
  314.        if (new->line == NULL)
  315.         int_error("not enough memory to store help file", -1);
  316.        (void) strcpy(new->line, text);
  317.     } else
  318.      new->line = NULL;
  319.  
  320.     new->next = NULL;
  321.  
  322.     return(new);
  323. }
  324.  
  325. /* Add this keyword to the keys list, with the given text */
  326. static LINKEY *
  327. storekey(key)
  328.     char *key;
  329. {
  330.     LINKEY *new;
  331.  
  332.     key[strlen(key)-1] = '\0'; /* cut off \n  */
  333.  
  334.     new = (LINKEY *)malloc(sizeof(LINKEY));
  335.     if (new == NULL)
  336.      int_error("not enough memory to store help file", -1);
  337.     new->key = (char *) malloc((unsigned int)(strlen(key)+1));
  338.     if (new->key == NULL)
  339.      int_error("not enough memory to store help file", -1);
  340.     (void) strcpy(new->key, key);
  341.  
  342.     /* add to front of list */
  343.     new->next = keylist;
  344.     keylist = new;
  345.     keycount++;
  346.     return(new);
  347. }
  348.  
  349. /* we sort the keys so we can use binary search later */
  350. /* We have a linked list of keys and the number.
  351.  * to sort them we need an array, so we reform them into an array,
  352.  * and then throw away the list.
  353.  */
  354. static void
  355. sortkeys()
  356. {
  357.     LINKEY *p,*n;            /* pointers to linked list */
  358.     int i;                /* index into key array */
  359.     
  360.     /* allocate the array */
  361.     keys = (KEY *)malloc((unsigned int)((keycount+1) * sizeof(KEY)));
  362.     if (keys == NULL)
  363.      int_error("not enough memory to store help file", -1);
  364.     
  365.     /* copy info from list to array, freeing list */
  366.     for (p = keylist, i = 0; p != NULL; p = n, i++) {
  367.        keys[i].key = p->key;
  368.        keys[i].pos = p->pos;
  369.        keys[i].text = p->text;
  370.        keys[i].primary = p->primary;
  371.        n = p->next;
  372.        free( (char *)p );
  373.     }
  374.  
  375.     /* a null entry to terminate subtopic searches */
  376.     keys[keycount].key = NULL;
  377.     keys[keycount].pos = 0;
  378.     keys[keycount].text = NULL;
  379.  
  380.     /* sort the array */
  381.     /* note that it only moves objects of size (two pointers + long + int) */
  382.     /* it moves no strings */
  383.     qsort((char *)keys, keycount, sizeof(KEY), keycomp);
  384. }
  385.  
  386. static int
  387. keycomp(a, b)
  388.     KEY *a,*b;
  389. {
  390.     return (strcmp(a->key, b->key));
  391. }
  392.  
  393. /* Free the help file from memory. */
  394. /* May be called externally if space is needed */
  395. void
  396. FreeHelp()
  397. {
  398.     int i;                /* index into keys[] */
  399.     LINEBUF *t, *next;
  400.  
  401.     if (keys == NULL)
  402.      return;
  403.  
  404.     for (i = 0; i < keycount; i++) {
  405.        free( (char *)keys[i].key );
  406.        if (keys[i].primary)   /* only try to release text once! */
  407.        for (t = keys[i].text; t != NULL; t = next) {
  408.           free( (char *)t->line );
  409.           next = t->next;
  410.           free( (char *)t );
  411.        }
  412.     }
  413.     free( (char *)keys );
  414.     keys = NULL;
  415.     keycount = 0;
  416. #ifdef MSDOS
  417.     (void) fclose(helpfp);
  418. #endif
  419. }
  420.  
  421. /* FindHelp:
  422.  *  Find the key that matches the keyword.
  423.  *  The keys[] array is sorted by key.
  424.  *  We could use a binary search, but a linear search will aid our
  425.  *  attempt to allow abbreviations. We search for the first thing that
  426.  *  matches all the text we're given. If not an exact match, then
  427.  *  it is an abbreviated match, and there must be no other abbreviated
  428.  *  matches -- for if there are, the abbreviation is ambiguous. 
  429.  *  We print the ambiguous matches in that case, and return not found.
  430.  */
  431. static KEY *                /* NULL if not found */
  432. FindHelp(keyword)
  433.     char *keyword;            /* string we look for */
  434. {
  435.     KEY *key;
  436.     int len = strlen(keyword);
  437.     int compare;
  438.  
  439.     for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
  440.        compare = strncmp(keyword, key->key, len);
  441.        if (compare == 0)    /* we have a match! */
  442.         if (!Ambiguous(key, len)) {
  443.             /* non-ambiguous abbreviation */
  444.             (void) strcpy(keyword, key->key); /* give back the full spelling */
  445.             return(key);        /* found!! */
  446.         }
  447.     }
  448.  
  449.     /* not found, or ambiguous */
  450.     return(NULL);
  451. }
  452.  
  453. /* Ambiguous:
  454.  * Check the key for ambiguity up to the given length.
  455.  * It is ambiguous if it is not a complete string and there are other
  456.  * keys following it with the same leading substring.
  457.  */
  458. static boolean
  459. Ambiguous(key, len)
  460.     KEY *key;
  461.     int len;
  462. {
  463.     char *first;
  464.     char *prev;
  465.     boolean status = FALSE;    /* assume not ambiguous */
  466.     int compare;
  467.     int sublen;
  468.  
  469.     if (key->key[len] == '\0')
  470.      return(FALSE);
  471.     
  472.     for (prev = first = key->key, compare = 0, key++;
  473.         key->key != NULL && compare == 0; key++) {
  474.        compare = strncmp(first, key->key, len);
  475.        if (compare == 0) {
  476.           /* So this key matches the first one, up to len.
  477.            * But is it different enough from the previous one
  478.            * to bother printing it as a separate choice?
  479.            */
  480.           sublen = instring(prev+len, ' ');
  481.           if (strncmp(key->key, prev, len+sublen) != 0) {
  482.              /* yup, this is different up to the next space */
  483.              if (!status) {
  484.                 /* first one we have printed is special */
  485.                 fprintf(stderr, 
  486.                        "Ambiguous request '%.*s'; possible matches:\n",
  487.                        len, first);
  488.                 fprintf(stderr, "\t%s\n", prev);
  489.                 status = TRUE;
  490.              }
  491.              fprintf(stderr, "\t%s\n", key->key);
  492.              prev = key->key;
  493.           }
  494.        }
  495.     }
  496.     
  497.     return(status);
  498. }
  499.  
  500. /* PrintHelp:
  501.  * print the text for key
  502.  */
  503. static void
  504. PrintHelp(key, subtopics)
  505.     KEY *key;
  506.     boolean *subtopics;        /* (in) - subtopics only? */
  507.                         /* (out) - are there subtopics? */
  508. {
  509.     LINEBUF *t;
  510. #ifdef MSDOS
  511.     char buf[BUFSIZ];        /* line from help file */
  512. #endif
  513.  
  514.     StartOutput();
  515.  
  516.     if (subtopics == NULL || !*subtopics) {
  517. #ifdef MSDOS
  518.        fseek(helpfp,key->pos,0);
  519.        while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  520.             && (buf[0] != KEYFLAG) ) {
  521.           OutLine(buf);
  522.        }
  523. #else
  524.        for (t = key->text; t != NULL; t = t->next)
  525.         OutLine(t->line);        /* print text line */
  526. #endif
  527.     }
  528.  
  529.     ShowSubtopics(key, subtopics);
  530.     OutLine("\n");
  531.  
  532.     EndOutput();
  533. }
  534.  
  535. /* ShowSubtopics:
  536.  *  Print a list of subtopic names
  537.  */
  538. #define PER_LINE 4
  539.  
  540. static void
  541. ShowSubtopics(key, subtopics)
  542.     KEY *key;                /* the topic */
  543.     boolean *subtopics;        /* (out) are there any subtopics */
  544. {
  545.     int subt = 0;            /* printed any subtopics yet? */
  546.     KEY *subkey;            /* subtopic key */
  547.     int len;                /* length of key name */
  548.     char line[BUFSIZ];        /* subtopic output line */
  549.     char *start;            /* position of subname in key name */
  550.     int sublen;            /* length of subname */
  551.     int pos;
  552.     char *prev = NULL;        /* the last thing we put on the list */
  553.  
  554.     *line = '\0';
  555.     len = strlen(key->key);
  556.  
  557.     for (subkey = key+1; subkey->key != NULL; subkey++) {
  558.        if (strncmp(subkey->key, key->key, len) == 0) {
  559.           /* find this subtopic name */
  560.           start = subkey->key + len;
  561.           if (len > 0)
  562.             if (*start == ' ')
  563.              start++;        /* skip space */
  564.             else
  565.              break;        /* not the same topic after all  */
  566.           else            /* here we are looking for main topics */
  567.             if (!subkey->primary)
  568.              continue;    /* not a main topic */
  569.           sublen = instring(start, ' ');
  570.           if (prev == NULL || strncmp(start, prev, sublen) != 0) {
  571.              if (subt == 0) {
  572.                 subt++;
  573.                 if (len)
  574.                   (void) sprintf(line, "\nSubtopics available for %s:\n", 
  575.                         key->key);
  576.                 else
  577.                   (void) sprintf(line, "\nHelp topics available:\n");
  578.                 OutLine(line);
  579.                 *line = '\0';
  580.                 pos = 0;
  581.              }
  582.              if (pos == PER_LINE) {
  583.                 (void) strcat(line, "\n");
  584.                 OutLine(line);
  585.                 *line = '\0';
  586.                 pos = 0;
  587.              }
  588.              (void) strcat(line, "\t");
  589.              (void) strncat(line, start, sublen);
  590.              pos++;
  591.              prev = start;
  592.           }
  593.        } else {
  594.           /* new topic */
  595.           break;
  596.        }
  597.     }
  598.     
  599.     /* put out the last line */
  600.     if (subt > 0 && pos > 0) {
  601.        (void) strcat(line, "\n");
  602.        OutLine(line);
  603.     }
  604.     
  605. /*
  606.     if (subt == 0) {
  607.        OutLine("\n");
  608.        OutLine("No subtopics available\n");
  609.     }
  610. */
  611.     
  612.     if (subtopics)
  613.      *subtopics = (subt != 0);
  614. }
  615.  
  616.  
  617. /* StartOutput:
  618.  * Open a file pointer to a pipe to user's $PAGER, if there is one,
  619.  * otherwise use our own pager.
  620.  */
  621. static void
  622. StartOutput()
  623. {
  624. #ifdef unix
  625.     char *pager_name = getenv("PAGER");
  626.     extern FILE *popen();
  627.  
  628.     if (pager_name != NULL && *pager_name != '\0')
  629.      if ((outfile = popen(pager_name, "w")) != (FILE *)NULL)
  630.        return;            /* success */
  631.     outfile = stderr;
  632.     /* fall through to built-in pager */
  633. #endif
  634.  
  635.     /* built-in pager */
  636.     pagelines = 0;
  637. }
  638.  
  639. /* write a line of help output  */
  640. /* line should contain only one \n, at the end */
  641. static void
  642. OutLine(line)
  643.     char *line;
  644. {
  645.     int c;                /* dummy input char */
  646. #ifdef unix
  647.     if (outfile != stderr) {
  648.        fputs(line, outfile);
  649.        return;
  650.     }
  651. #endif
  652.  
  653.     /* built-in dumb pager */
  654.     /* leave room for prompt line */
  655.     if (pagelines >= SCREENSIZE - 2) {
  656.        printf("Press return for more: ");
  657.        do 
  658.         c = getchar();
  659.        while (c != EOF && c != '\n');
  660.        pagelines = 0;
  661.     }
  662.     fputs(line, stderr);
  663.     pagelines++;
  664. }
  665.  
  666. static void
  667. EndOutput()
  668. {
  669. #ifdef unix
  670.     extern int pclose();
  671.  
  672.     if (outfile != stderr)
  673.      (void) pclose(outfile);
  674. #endif
  675. }
  676.  
  677.